package njust.csc.aecore.analysis

import com.github.difflib.DiffUtils
import com.github.difflib.patch.Patch
import io.github.oshai.kotlinlogging.KotlinLogging
import njust.csc.aecore.entity.APK
import njust.csc.aecore.entity.AnalysisStatus
import njust.csc.aecore.entity.DeleteStatusReason
import njust.csc.aecore.entity.InstallStatusReason
import njust.csc.aecore.utils.*
import java.nio.file.Path

private val logger = KotlinLogging.logger {}
/**
 * @Author ZhouHang
 * @Date 24/6/9 周日
 */
class DynamicExplorer (private val savePath: Path? = null) {
    /**
     * 整个动态分析流程的状态控制
     * */
    val status = AnalysisStatus()

    /**
     * 给定相对路径索引 APK 文件
     * 适用无法静态分析的 APK，应该不会出现连 Manifest 都无法分析的情况
     * */
    fun fromApkFile(relativePath: String) {
        val apk = APK()
        apk.absolutePath = PathUtils.inputResolve(relativePath).toAbsolutePath().toString()
        fromApk(apk)
    }

    /**
     * 对静态分析得到的 json 数据分析
     * @param apkInfo 静态分析得到的 apk 基本信息字符串
     * */
    fun fromApk(apkInfo: String) {
        val apk = SerializeUtils.load(apkInfo)
        if (apk.packageName == "")
            logger.info { "${apk.relativePath}: 未找到 package name，尝试动态解析包名" }
        fromApk(apk)
    }

    /**
     * 对 apk 对象分析
     * @param apk 待动态分析的 apk
     * */
    fun fromApk(apk: APK) {
        testExistDevice()
        if (!status.existEnvironment) return

        logger.info { "开始动态分析 ${apk.name} ${apk.packageName} (${apk.relativePath})" }
        clearDevice  ()
        installAPK   (apk)  // 若静态分析无法得到包名，若能安装应用，则能获得包名
        if (!status.installStatus) return

        testRun      (apk)
        if (!status.testRunStatus) {
            logger.info { "${apk.name} 发生闪退，无法动态分析" }
            uninstallAPK (apk)
            return
        }
        startCapture (apk)
        if (!status.captureServiceStatus) return
        startUsing   (apk)
        stopCapture  ()
        pullPCAP     (apk)

        uninstallAPK (apk)
    }

    /**
     * 测试模拟器系统环境，若不存在环境，则直接不进行后续分析
     * */
    private fun testExistDevice() {
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf("ls")) { line ->
            if (line.contains("no devices") or line.contains("not found")) {
                logger.info { "没有安卓环境，停止动态分析" }
                status.existEnvironment = false
            }
        }
    }

    /**
     * 清除设备中的残留文件
     * 如 data/app 下的 tmp 文件，ape 生成文件，截图残留文件等
     * */
    private fun clearDevice() {
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf("rm", "-rf", "/data/app/*.tmp")) {}
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf("rm", "-rf", "/sdcard/sata*")) {}
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf("rm", "-rf", "/sdcard/*.png")) {}
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf("rm", "-rf", "/sdcard/*.uix")) {}
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf("rm", "-rf", "/sdcard/*running-minutes*")) {}
    }

    /**
     * 安装应用
     * @param apk: 待安装应用的所有信息
     * */
    private fun installAPK(apk: APK) {
        // 这里包名可能为空，通过两次 adb shell "pm list packages" 命令获得包名
        val packageNameListStart = mutableListOf<String>()
        if (apk.packageName == "") {
            CommandUtils.runCommand(CommandUtils.shellCmd + listOf(
                "pm", "list", "packages"
            )) {
                packageNameListStart.add(it.substring(8))
            }
        }
        // 安装程序
        CommandUtils.runCommand(CommandUtils.installCmd + listOf(apk.absolutePath)) {
            line ->
            if (line.startsWith("Success")) {
                logger.info { "${apk.packageName} 安装成功" }
                status.installStatus = true
                status.installStatusReason = InstallStatusReason.fromString("Success")
            } else if (line.startsWith("Failure")) {
                val reason = RegexUtils.failReason.find(line)?.value ?: "Unknown"
                logger.info { "${apk.packageName} 安装失败, 原因: $reason" }
                // 由于 APK 已安装导致的安装失败可以继续走流程
                status.installStatusReason = InstallStatusReason.fromString(reason)
                status.installStatus = (InstallStatusReason.fromString(reason) == InstallStatusReason.INSTALL_FAILED_ALREADY_EXISTS)
            }
        }
        // 若安装成功且没有包名，则通过两次 diff 来获取包名
        if (status.installStatus) {
            val packageNameListEnd = mutableListOf<String>()
            if (apk.packageName == "") {
                CommandUtils.runCommand(CommandUtils.shellCmd + listOf(
                    "pm", "list", "packages"
                )) {
                    packageNameListEnd.add(it.substring(8))
                }
                val patch: Patch<String> = DiffUtils.diff(packageNameListStart, packageNameListEnd)
                if (patch.deltas.size != 1)
                    logger.error { "${apk.relativePath} 动态包名解析失败" }
                else {
                    val packageNames = patch.deltas[0].target.lines
                    if (packageNames.size != 1)
                        logger.error { "${apk.relativePath} 动态包名解析失败，结果为 $packageNames" }
                    else
                        apk.packageName = packageNames[0]
                }
            }
        }
    }

    /**
     * 卸载应用
     * @param apk: 待卸载应用的所有信息
     * */
    private fun uninstallAPK(apk: APK) {
        CommandUtils.runCommand(CommandUtils.uninstallCmd + listOf(apk.packageName)) {
            line ->
            if (line.startsWith("Success")) {
                logger.info { "${apk.packageName} 卸载成功" }
                status.uninstallStatus = true
                status.uninstallStatusReason = DeleteStatusReason.fromString("Success")
            } else if (line.startsWith("Failure")) {
                val reason = RegexUtils.failReason.find(line)?.value ?: "Unknown"
                logger.info { "卸载失败, 原因: $reason" }
                status.installStatusReason = InstallStatusReason.fromString(reason)
                status.uninstallStatus = false
            }
        }
        Thread.sleep(2000)
    }

    /**
     * 测试 apk 是否能够运行
     * */
    private fun testRun(apk: APK) {
        // Monkey 通过包名启动应用
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf(
            "monkey", "-p", apk.packageName, "-v", "1"
        )) {}

        // 等待应用启动并运行一小段时间
        Thread.sleep(2000)

        // ps 查看该应用是否依然在后台存在
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf("ps")) { line ->
            if (line.contains(apk.packageName)) {
                status.testRunStatus = true  // 该 APK 能够进行动态分析
            }
        }

        if (status.testRunStatus) {
            CommandUtils.runCommand(CommandUtils.shellCmd + listOf("am", "force-stop", apk.packageName)) { }
        }
    }

    /**
     * 开启 PCAPdroid 抓包
     * */
    private fun startCapture(apk: APK) {
        CommandUtils.runCommand(CommandUtils.amCmd + listOf(
            "-e", "action", "start",
            "-e", "pcap_dump_mode", "pcap_file",
//        "-e", "tls_decryption", "true",
            "-e", "app_filter", apk.packageName,
            "-e", "pcap_name", "${apk.packageName}.pcap",
            "-n", "com.emanuelef.remote_capture/.activities.CaptureCtrl"
        )) {}

        // 点击 Allow
        Thread.sleep(2000)
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf("input", "tap", "700", "1300")) {}
        Thread.sleep(2000)  // 点击 Allow 之后等一小段时间，否则模拟器可能会卡死
        status.captureServiceStatus = true
    }

    /**
     * 停止 PCAPdroid 抓包
     * */
    private fun stopCapture() {
        CommandUtils.runCommand(CommandUtils.amCmd + listOf(
            "-e", "action", "stop",
            "-n", "com.emanuelef.remote_capture/.activities.CaptureCtrl"
        )) {}

        // 点击 Allow
        Thread.sleep(2000)
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf("input", "tap", "700", "1300")) {}
        Thread.sleep(2000)
        status.captureServiceStatus = false
    }

    /**
     * 获取抓包结果
     * */
    private fun pullPCAP(apk: APK) {
        val pcapPath = savePath ?: PathUtils.getPCAPDir()
        CommandUtils.runCommand(CommandUtils.pullCmd + listOf(
            "/sdcard/Download/PCAPdroid/${apk.packageName}.pcap",
            pcapPath.toAbsolutePath().toString()
        )) {}

        // 删除文件，避免不必要的空间占用
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf(
                "rm", "-rf", "/sdcard/Download/PCAPdroid/${apk.packageName}.pcap"
        )) {}
    }

    /**
     * APE UI 自动遍历 + 截图
     * */
    private fun startUsing(apk: APK) {
        val runningTime = 1  // 单位：分钟
        // Fastbot 自动化
        CommandUtils.runCommand(CommandUtils.shellCmd + listOf(
            "CLASSPATH=/data/local/tmp/monkeyq.jar:/data/local/tmp/framework.jar:/data/local/tmp/fastbot-thirdpart.jar",
            "exec", "app_process", "/system/bin", "com.android.commands.monkey.Monkey",
            "-p", apk.packageName,
            "--agent", "robot",
            "--running-minutes", "$runningTime",
            "--throttle", "210", "-v", "-v"
        )) {}

        val fastbotResult = "fastbot-${apk.packageName}--running-minutes-${runningTime}"
        val fastbotResultPath = savePath ?: PathUtils.getScreencapDir(apk.packageName)
        CommandUtils.runCommand(CommandUtils.pullCmd + listOf(
            "/sdcard/${fastbotResult}/.",  // FastBot 默认结果输出位置
            fastbotResultPath.toAbsolutePath().toString()
        )) {}
        status.apeStatus = true
    }
}